<?php
//linux下开启开启进程守护：  nohup php swooleServer.php > /dev/null 2> /dev/null &

Class SwooleChat
{
    // websocket连接信息
    protected $host = '0.0.0.0';
    protected $port = 9502;
    protected $ws;

    // mysql连接信息
    protected $user_name = 'root';
    protected $pwd = 'root';
    protected $db_name = 'chat';
    protected $conn;

    /**
     * 初始化构造函数
     */
    public function __construct()
    {
        $this->ws = new Swoole\WebSocket\Server($this->host, $this->port);
    }

    /**
     * 服务端执行入口
     */
    public function run()
    {
        $this->open();
        $this->message();
        $this->close();
        $this->ws->start();
    }

    /**
     * WebSocket 建立连接后进行握手
     * 若使用onHandShake，将不会再执行 onopen，需要自己实现 onopen
     */
    private function onHandShake()
    {
        $this->ws->on('handShake', function ($request, $response) {
            if (!isset($request->header["origin"]) || $request->header["origin"] !== "http://chat.yisanwulian.com" ) {
                $response->end();
                return FALSE;
            }

            // websocket握手连接算法验证
            $secWebSocketKey = $request->header['sec-websocket-key'];
            $patten = '#^[+/0-9A-Za-z]{21}[AQgw]==$#';
            if (0 === preg_match($patten, $secWebSocketKey) || 16 !== strlen(base64_decode($secWebSocketKey))) {
                $response->end();
                return false;
            }

            $key = base64_encode(
              sha1(
                $request->header['sec-websocket-key'] . '258EAFA5-E914-47DA-95CA-C5AB0DC85B11',
                true
              )
            );

            $headers = [
              'Upgrade' => 'websocket',
              'Connection' => 'Upgrade',
              'Sec-WebSocket-Accept' => $key,
              'Sec-WebSocket-Version' => '13',
            ];

            // WebSocket connection to 'ws://127.0.0.1:9502/'
            // failed: Error during WebSocket handshake:
            // Response must not include 'Sec-WebSocket-Protocol' header if not present in request: websocket
            if (isset($request->header['sec-websocket-protocol'])) {
                $headers['Sec-WebSocket-Protocol'] = $request->header['sec-websocket-protocol'];
            }

            foreach ($headers as $key => $val) {
                $response->header($key, $val);
            }

            $response->status(101);
            $response->end();
        });
    }

    /**
     * 监听WebSocket连接打开
     */
    private function open()
    {
        $this->ws->on('open', function ($ws, $request) {
            // 获取参数
            $fd = $request->fd;
            $user_name = trim(strip_tags($request->get['user_name']));

            //鉴权
            if (!isset($request->header["origin"]) || $request->header["origin"] !== "http://www.foo.com" ) {
                $ws->disconnect($fd);
                return FALSE;
            }

            // 连接数据库
            $this->initConn();

            // 新用户入库，老用户更新状态
            $sql = "INSERT INTO chat_user (fd, user_name, add_time,online_status) VALUES (%d,'%s',%d,1) ON DUPLICATE KEY UPDATE fd=VALUES(fd),online_status=1";
            $sql = sprintf($sql, $fd, $user_name, time());
            $this->conn->query($sql);

            //获取在线用户列表 推送上线通知
            $user_list = [];
            $cur_user_id = 0;
            $time = date('H:i:s');

            $result = $this->conn->query("SELECT fd,id,user_name FROM chat_user WHERE online_status = 1 ORDER BY fd DESC");
            if (mysqli_num_rows($result) > 0) {
                while ($row = mysqli_fetch_assoc($result)) {

                    if ($row["fd"] == $fd) {
                        $cur_user_id = $row["id"]; //当前用户id
                    } else {
                        $user_list[] = $row; //在线用户列表

                        if ($ws->isEstablished($row['fd'])) {
                            $ws->push($row['fd'], json_encode([
                              "type" => "on",
                              "msg"  => '欢迎【' . $user_name . '】进入聊天室 ' . $time,
                              "data" => $row
                            ]));
                        }
                    }
                }
            }

            // 当前登录用户信息
            $data["mine"] = $cur_user_id;

            // 在线列表
            $user_list && $data["online"] = $user_list;

            // 最近聊天记录
            $content = [];
            $sql     = "SELECT user_id,user_name,to_name,content,create_at FROM chat_content WHERE (user_id=%d OR user_name = '%s' OR to_name='%s') AND create_at > '%s' ORDER BY id ASC LIMIT 20";
            $sql     = sprintf($sql, $cur_user_id, $user_name, '全体', date("Y-m-d"));
            $result  = $this->conn->query($sql);
            if (mysqli_num_rows($result) > 0) {          
                while ($row = mysqli_fetch_assoc($result)) {
                    $row["user_name"] .= " -> ". $row["to_name"];
                    unset($row["to_name"]);
                    $content[] = $row;
                }
            }
            $content && $data["last"] = $content;

            //
            $ws->push($fd, json_encode([
              "type"  => "open",
              "data"  => $data,
            ], JSON_UNESCAPED_UNICODE));
        });
    }

    /**
     * 监听WebSocket消息
     */
    private function message()
    {
        $this->ws->on('message', function ($ws, $frame) {
            $this->initConn();
            // 聊天消息
            $fd = $frame->fd;
            $param = json_decode($frame->data, true);
            $type  = $param["type"] ?? "chat";

            switch ($type) {
                case "ping": //心跳
                    $this->return['type'] = 'pong';
                    $ws->push($fd, json_encode(["type"=>"pong"]));
                    break;
                default: //聊天
                    $message = addslashes(strip_tags(trim($param["message"])));
					if ($message) {
						// 用户名称
						$res = $this->conn->query("SELECT id,user_name FROM chat_user WHERE fd = {$fd} LIMIT 1");
						$user = $res->fetch_assoc();
						$user_name = $user['user_name'];

						// 推送发送的消息
                        $return = [
                          "type" => "chat",
                          "msg"  => $message,
                        ];

						if ($param["fd"]) {
							$res = $this->conn->query("SELECT id,user_name FROM chat_user WHERE fd = {$param["fd"]} LIMIT 1");
							$to = $res->fetch_assoc();
							$to_name = $to['user_name'];
							//一对一
                            $return['data'] = ['user_name' => $user_name. "->" . $to_name, 'from' => $fd, 'to' => $param['fd'], 'time' => date('Y-m-d H:i:s')];
							$ws->push($fd, json_encode($return));
							$ws->push($param['fd'], json_encode($return));
						} else {
							$to_name = "全体";
							//全体
							$result = $this->conn->query("SELECT fd,user_name FROM chat_user WHERE online_status = 1 ORDER BY id desc");
							if (mysqli_num_rows($result) > 0) {
								while ($row = mysqli_fetch_assoc($result)) {
                                    $return['data'] = ['user_name' => $user_name . "->全体", 'from' => $fd, 'to' => $row['fd'], 'time' => date('Y-m-d H:i:s')];
                                    if ($ws->isEstablished($row['fd'])) {
                                        $ws->push($row['fd'], json_encode($return));
                                    }
								}
							}
						}
			
						//数据入库
						$sql = "INSERT INTO chat_content (user_id, user_name, to_name, content, create_at) VALUES (%d,'%s','%s','%s','%s') ";
						$sql = sprintf($sql, $user['id'], $user_name, $to_name, $message, date("Y-m-d H:i:s"));
						$this->conn->query($sql);					
					}
                    break;    
            }
        });
    }

    /**
     * 监听WebSocket连接关闭
     */
    private function close()
    {
        $this->ws->on('close', function ($ws, $fd) {
            $this->initConn();
            // 用户名称
            $res = $this->conn->query("SELECT user_name FROM chat_user WHERE fd = {$fd} LIMIT 1");
            $user = $res->fetch_assoc();

            if ($user) {
                // 用户下线
                $this->conn->query("UPDATE chat_user SET online_status = 0 WHERE fd = $fd");

                // 用户列表
                $sql = "SELECT fd,user_name FROM chat_user WHERE online_status = 1 ORDER BY id desc";
                $result = $this->conn->query($sql);
                if (mysqli_num_rows($result) > 0) {
                    // 给其他用户推送下线通知
                    while ($row = mysqli_fetch_assoc($result)) {
                        if ($row['fd'] != $fd) {
                            $return = [
                              "type" => "off",
                              "msg"  => '【' . $user['user_name'] . '】离开聊天室 ' . date('H:i:s'),
                              "data" => ['fd' => $fd]
                            ];
                            if ($ws->isEstablished($row['fd'])) {
                                $ws->push($row['fd'], json_encode($return));
                            }
                        }
                    }
                }
            }

        });
    }

    /**
     * MySQL连接
     */
    private function initConn()
    {
        // 建立mysql数据库链接
        $this->conn = new mysqli("127.0.0.1", $this->user_name, $this->pwd, $this->db_name);
        mysqli_query($this->conn, "set character set 'utf8'");
        mysqli_query($this->conn, "set names 'utf8'");
    }
}

// 运行服务器端
$chat = new SwooleChat();
$chat->run();
